using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Pong_Take1
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont font;
        private string scoreTxt1, scoreTxt2, pauseTxt;
        private byte score1, score2;
        private Ball ball;        
        private Bar bar, bar2;
        private Vector2 ballSpeed;                
        private byte barSpeed;
        private bool paused;
        private Random rand = new Random();

        private bool consoleActivated;
        Texture2D consoleBackground;
        Rectangle consoleRect;
        Rectangle inputRect;
        Rectangle cursorRect;
        Texture2D inputBackground;
        SpriteFont consoleFont;
        private const int consoleHeight = 16;
        private List<string> consoleCommands;
        private int commandsCount;
        private int maxChar;
        private string inputCommand;        
        private const double BlinkRate = 1000 / 5;
        private double cursorBlinkRate;
        private List<string> commandsList;
        private const int commandsLimit = 10;
        private int commandIndexUp;
        private int commandIndexDown;
        private int fontWidth, fontHeight;
        Color player1Color, player2Color;

        Color backgroundColor;

        SoundEffect sound;
        SoundEffect BackgoundSound;
        SoundEffectInstance soundLoop;
               
        private InputProcessor input;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            input = new InputProcessor(this);
            Components.Add(input);

            consoleActivated = false;            

            ballSpeed = new Vector2(rand.Next(5) + 8, rand.Next(5) + 10);
            barSpeed = 16;
            paused = true;            
            scoreTxt1 = "Player 1: ";
            scoreTxt2 = "Player 2: ";
            score1 = 0; score2 = 0;
            pauseTxt = "Game paused, press space or start to resume";

            consoleRect = new Rectangle(0, 0, 700, 500);
            inputRect = new Rectangle(5, 0, 690, 10);
            consoleCommands = new List<string>(consoleHeight);
            commandsList = new List<string>(commandsLimit);
            inputCommand = null;
            //keyRepeatRate = RepeatRate;
            commandIndexUp = 0;
            commandIndexDown = 0;

            backgroundColor = new Color(20,200,200);
            player1Color = Color.White;
            player2Color = Color.White;

            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            graphics.ApplyChanges();            
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here                                            

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here

            bar = new Bar(this, new Vector2(0, 0));
            Components.Add(bar);
            bar2 = new Bar(this, new Vector2(1180, 0));
            Components.Add(bar2);
            ball = new Ball(this, new Vector2(640, 360));
            Components.Add(ball);               

            ball.ballTexture = this.Content.Load<Texture2D>("ball_small");
            bar2.barTexture = this.Content.Load<Texture2D>("Bar");
            bar.barTexture = this.Content.Load<Texture2D>("Bar2");

            font = this.Content.Load<SpriteFont>("SpriteFont1");

            bar2.barPos = new Vector2(graphics.PreferredBackBufferWidth - bar2.barTexture.Width, 
                    graphics.PreferredBackBufferHeight / 2 - bar2.barTexture.Height / 2);
            bar.barPos = new Vector2(0, graphics.PreferredBackBufferHeight / 2 - bar.barTexture.Height / 2);

            consoleFont = this.Content.Load<SpriteFont>("consoleFont");
            consoleBackground = new Texture2D(GraphicsDevice, 1, 1);
            consoleBackground.SetData(new Color[] { Color.Black });

            fontWidth = Convert.ToInt16(consoleFont.MeasureString("A").X);
            fontHeight = Convert.ToInt16(consoleFont.MeasureString("A").Y);

            maxChar = ((consoleRect.Width - 20) / fontWidth);

            inputBackground = new Texture2D(GraphicsDevice, 1, 1);
            inputBackground.SetData(new Color[] { Color.WhiteSmoke });
            inputRect.Y = consoleRect.Height - 10 - fontHeight - 5;
            inputRect.Height = fontHeight + 10;

            cursorRect = new Rectangle(0, 0, 10, fontHeight + 2);
            cursorRect.X = consoleRect.Location.X + 10;
            cursorRect.Y = consoleRect.Height - fontHeight - 10 - 1;


            sound = Content.Load<SoundEffect>("sound");

            BackgoundSound = Content.Load<SoundEffect>("BackgroundMusic");
            soundLoop = BackgoundSound.CreateInstance();
            soundLoop.IsLooped = true;

            soundLoop.Play();

            consoleStartUp();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (input.isKeyPressed(Keys.Escape) || input.isButtonPressed(Buttons.Back, PlayerIndex.One))
                this.Exit();
            
            if (!consoleActivated)
            {
                if (input.isKeyPressed(Keys.C))
                    consoleActivated = true;

                if (input.isKeyPressed(Keys.Space) || input.isButtonPressed(Buttons.Start, PlayerIndex.One) || input.isButtonPressed(Buttons.Start, PlayerIndex.Two))
                    paused = !paused;

                // If the user hasn't paused, Update normally
                if (!paused)
                {
                    soundLoop.Resume();
                    Simulate(gameTime);
                }
                else
                    soundLoop.Pause();
            }
            else
            {
                soundLoop.Pause();                
                consoleSimulate(gameTime);
            }

            base.Update(gameTime);
        }

        protected void Simulate(GameTime gameTime)
        {
            //Check if the ball is moving to the left or right            
            if (ballSpeed.X < 0)
            {
                //it's moving to the left
                if (ball.ballBound.Intersects(bar.barBound))
                {
                    //check collision and see if it's coming from above or below the bar's center
                    if (bar.barPos.Y + bar.barTexture.Height / 2 < ball.ballCenter.Y)
                    {                        
                        //it's coming from below
                        //calculate the distance between the ball's center and this line instead of calculating the angle to see if it's hitting
                        //the corner or not. dCal function is used to calculate the distance of the bottom's right corner, dCal2 is for the top's right corner
                        //
                        //   \ |
                        //   _\|
                        //     \
                        //      \
                        
                        if (dCalc(bar.barBotRight, ball.ballCenter) <= ball.ballTexture.Width / 4)
                        {
                            //if the distance is smaller than 1/2 of the ball's radiance, then it hit the corner, bounce back
                            ballSpeed.X *= -1;
                            ballSpeed.Y *= -1;
                        }
                        else
                        {
                            if (ball.ballCenter.Y > bar.barBotRight.Y)
                                ballSpeed.Y *= -1;
                            else
                                ballSpeed.X *= -1;
                        }                        
                    }
                    else
                    {
                        //it's coming from above, apply the previous steps again
                        if (dCalc2(bar.barTopRight, ball.ballCenter) <= ball.ballTexture.Width / 4)
                        {
                            ballSpeed.X *= -1;
                            ballSpeed.Y *= -1;
                        }
                        else
                        {
                            if (ball.ballCenter.Y < bar.barTopRight.Y)
                                ballSpeed.Y *= -1;
                            else
                                ballSpeed.X *= -1;
                        }
                    }
                    sound.Play();
                }
            }
            else
            {
                //the ball's going right, do all the steps again
                if (ball.ballBound.Intersects(bar2.barBound))
                {
                    if (bar2.barPos.Y + bar2.barTexture.Height / 2 < ball.ballCenter.Y)
                    {
                        if (dCalc2(bar2.barBotLeft, ball.ballCenter) <= ball.ballTexture.Width / 4)
                        {
                            ballSpeed.X *= -1;
                            ballSpeed.Y *= -1;
                        }
                        else
                        {
                            if (ball.ballCenter.Y > bar2.barBotLeft.Y)
                                ballSpeed.Y *= -1;
                            else
                                ballSpeed.X *= -1;
                        }                        
                    }
                    else
                    {
                        if (dCalc(bar2.barPos, ball.ballCenter) <= ball.ballTexture.Width / 4)
                        {
                            ballSpeed.X *= -1;
                            ballSpeed.Y *= -1;
                        }
                        else
                        {
                            if (ball.ballCenter.Y < bar2.barPos.Y)
                                ballSpeed.Y *= -1;
                            else
                                ballSpeed.X *= -1;
                        }
                    }
                    sound.Play();
                }
            }            

            float upY = ball.ballPos.Y;
            float downY = ball.ballPos.Y + ball.ballTexture.Height;
            float rightX = ball.ballPos.X + ball.ballTexture.Width; ;
            float leftX = ball.ballPos.X;

            //when the ball hits the left & right screen's border, stop and restart the game
            if (rightX >= this.GraphicsDevice.Viewport.Width)
            {
                score1 += 1;
                ball.ballPos = new Vector2(graphics.PreferredBackBufferWidth / 2 - ball.ballTexture.Width / 2,
                    graphics.PreferredBackBufferHeight / 2 - ball.ballTexture.Width / 2);
                ballSpeed = new Vector2(rand.Next(5) + 8, rand.Next(5) + 10);
            }
            else if (leftX < 1)
            {
                score2 += 1;
                ball.ballPos = new Vector2(graphics.PreferredBackBufferWidth / 2 - ball.ballTexture.Width / 2,
                    graphics.PreferredBackBufferHeight / 2 - ball.ballTexture.Width / 2);
                ballSpeed = new Vector2(rand.Next(5) + 8, rand.Next(5) + 10);
            }

            //when the ball hits the top and bottom, bounce
            if (upY <= 0 || downY >= this.GraphicsDevice.Viewport.Height)
            {
                ballSpeed.Y = ballSpeed.Y * (-1);
                sound.Play();
            }

            //check if the bars are out of screen or not. if they are out of screen, stop the movement
            if (input.isKeyDown(Keys.W) || input.isLeftThumbstickMovedUp(PlayerIndex.One))
            {
                if (bar.barPos.Y > 0)
                    bar.barPos.Y -= barSpeed;
            }
            else if (input.isKeyDown(Keys.S) || input.isLeftThumbstickMovedDown(PlayerIndex.One))
            {
                if (bar.barBotLeft.Y < graphics.PreferredBackBufferHeight)
                    bar.barPos.Y += barSpeed;
            }
            if (input.isKeyDown(Keys.Down) || input.isLeftThumbstickMovedDown(PlayerIndex.Two) || input.isRightThumbstickMovedDown(PlayerIndex.One))
            {
                if (bar2.barBotRight.Y < graphics.PreferredBackBufferHeight)
                    bar2.barPos.Y += barSpeed;
            }
            else if (input.isKeyDown(Keys.Up) || input.isLeftThumbstickMovedUp(PlayerIndex.Two) || input.isRightThumbstickMovedUp(PlayerIndex.One))
            {
                if (bar2.barTopRight.Y > 0)
                    bar2.barPos.Y -= barSpeed;
            }
            
            //if(Keyboard.GetState().IsKeyDown(Keys.G))
            //{
                ball.ballPos.X += ballSpeed.X;
                ball.ballPos.Y += ballSpeed.Y;            
            //}
        }

        protected void consoleSimulate(GameTime gameTime)
        {
            
            Keys[] pressedKeys;
            pressedKeys = input.getCurrentKeyboardState().GetPressedKeys();                                
            foreach (Keys key in pressedKeys)
            {
                if (input.isKeyPressed(key))
                {
                    switch (key)
                    {
                        case Keys.Back:
                                if (inputCommand != null && inputCommand.Length > 0)
                                inputCommand = inputCommand.Remove(inputCommand.Length - 1, 1);
                            break;
                        case Keys.Space:                        
                                inputCommand = inputCommand + " ";
                            break;
                        case Keys.Enter:
                            if (inputCommand != null)
                            {
                                addCommand(inputCommand, ref consoleCommands);
                                if (commandsList.Count == commandsLimit)
                                    commandsList.RemoveAt(0);
                                commandsList.Add(inputCommand);
                                commandIndexUp = commandsList.Count - 1;
                                commandIndexDown = commandsList.Count;
                                if (!doCommand(inputCommand))
                                    addCommand("Command unavailable", ref consoleCommands);
                                inputCommand = null;
                            }
                            break;                    
                        case Keys.D1:
                            inputCommand += "1";
                            break;
                        case Keys.D0:
                            inputCommand += "0";
                            break;
                        case Keys.D2:
                            inputCommand += "2";
                            break;
                        case Keys.D3:
                            inputCommand += "3";
                            break;
                        case Keys.D4:
                            inputCommand += "4";
                            break;
                        case Keys.D5:
                            inputCommand += "5";
                            break;
                        case Keys.D6:
                            inputCommand += "6";
                            break;
                        case Keys.D7:
                            inputCommand += "7";
                            break;
                        case Keys.D8:
                            inputCommand += "8";
                            break;
                        case Keys.D9:
                            inputCommand += "9";
                            break;
                        case Keys.OemComma:
                            inputCommand += ",";
                            break;                           
                        case Keys.Up:
                            if (commandIndexUp > -1)
                            {
                                inputCommand = commandsList.ElementAt(commandIndexUp);
                                commandIndexDown = commandIndexUp + 1;
                                commandIndexUp--;
                            }
                            break;
                        case Keys.Down:
                            if (commandIndexDown < commandsList.Count)
                            {
                                inputCommand = commandsList.ElementAt(commandIndexDown);
                                commandIndexUp = commandIndexDown - 1;
                                commandIndexDown++;                                    
                            }
                            break;
                        default:
                            {
                                if(key >= Keys.A && key <= Keys.Z)
                                inputCommand += key.ToString().ToLower();
                            }
                            break;
                    }
                }
            }            
        }

        protected void consoleStartUp()
        {            
            addCommand("Welcome to Pong's console", ref consoleCommands);
            addCommand("Type \"Close\" (without quotes) to close the console", ref consoleCommands);
            addCommand("Type \"Exit\"(without quotes) to quit Pong", ref consoleCommands);
            addCommand("Type \"BackgroundColor R,G,B\" (without quotes) to change background color", ref consoleCommands);
            addCommand("type \"player1color R,G,B\" (without quotes) to change player1 color", ref consoleCommands);
            addCommand("type \"player2color R,G,B\" (without quotes) to change player2 color", ref consoleCommands);
            addCommand("Note: R, G, B must be positive numbers and smaller than 256", ref consoleCommands);
            commandIndexUp = -1;
            commandIndexDown = 11;
        }

        protected bool doCommand(string command)
        {
            bool isdoable = true;
            if (command.Equals("CLOSE", StringComparison.CurrentCultureIgnoreCase))
                consoleActivated = false;            
            else if (command.Equals("EXIT", StringComparison.CurrentCultureIgnoreCase))
                this.Exit();
            else if (command.StartsWith("BackgroundColor ", StringComparison.CurrentCultureIgnoreCase))
            {
                string temp = command.Substring(command.IndexOf(' ') + 1, command.Length - command.IndexOf(' ') - 1);
                try
                {
                    Byte red = Convert.ToByte(temp.Substring(0, temp.IndexOf(',')));
                    command = temp.Substring(temp.IndexOf(',') + 1, temp.Length - temp.IndexOf(',') - 1);
                    Byte green = Convert.ToByte(command.Substring(0, command.IndexOf(',')));
                    temp = command.Substring(command.IndexOf(',') + 1, command.Length - command.IndexOf(',') - 1);
                    Byte blue = Convert.ToByte(command.Substring(0, command.IndexOf(',')));
                    backgroundColor = new Color(red, green, blue);
                }
                catch (Exception)
                {
                    isdoable = false;
                }
            }
            else if (command.StartsWith("Player1Color ", StringComparison.CurrentCultureIgnoreCase))
            {
                string temp = command.Substring(command.IndexOf(' ') + 1, command.Length - command.IndexOf(' ') - 1);
                try
                {
                    Byte red = Convert.ToByte(temp.Substring(0, temp.IndexOf(',')));
                    command = temp.Substring(temp.IndexOf(',') + 1, temp.Length - temp.IndexOf(',') - 1);
                    Byte green = Convert.ToByte(command.Substring(0, command.IndexOf(',')));
                    temp = command.Substring(command.IndexOf(',') + 1, command.Length - command.IndexOf(',') - 1);
                    Byte blue = Convert.ToByte(command.Substring(0, command.IndexOf(',')));
                    player1Color = new Color(red, green, blue);
                }
                catch (Exception)
                {
                    isdoable = false;
                }
            }
            else if (command.StartsWith("Player2Color ", StringComparison.CurrentCultureIgnoreCase))
            {
                string temp = command.Substring(command.IndexOf(' ') + 1, command.Length - command.IndexOf(' ') - 1);
                try
                {
                    Byte red = Convert.ToByte(temp.Substring(0, temp.IndexOf(',')));
                    command = temp.Substring(temp.IndexOf(',') + 1, temp.Length - temp.IndexOf(',') - 1);
                    Byte green = Convert.ToByte(command.Substring(0, command.IndexOf(',')));
                    temp = command.Substring(command.IndexOf(',') + 1, command.Length - command.IndexOf(',') - 1);
                    Byte blue = Convert.ToByte(command.Substring(0, command.IndexOf(',')));
                    player2Color = new Color(red, green, blue);
                }
                catch (Exception)
                {
                    isdoable = false;
                }
            }
            else
                isdoable = false;
            return isdoable;
        }

        protected void addCommand(string input, ref List<string> list)
        {
            int segments = input.Length / maxChar;
            if (list.Count == consoleHeight)
                for (int i = 0; i <= segments; i++)
                    list.RemoveAt(i);
            if (segments > 0)
            {
                for (int i = 0; i <input.Length; i += maxChar)
                {                    
                    if (i + maxChar > input.Length - 1)
                        list.Add(input.Substring(i, input.Length - i));
                    else
                        list.Add(input.Substring(i, maxChar));                    
                }                               
            }
            else
                list.Add(input);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        /// 
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(backgroundColor);

            // TODO: Add your drawing code here            

            spriteBatch.Begin();

                spriteBatch.DrawString(font, scoreTxt1 + score1, new Vector2(102, 52), Color.Black * .5f);
                spriteBatch.DrawString(font, scoreTxt1 + score1, new Vector2(100, 50), player1Color);

                spriteBatch.DrawString(font, scoreTxt2 + score2, new Vector2(graphics.GraphicsDevice.Viewport.Width - 300 + 2, 50 + 2), Color.Black * .5f);
                spriteBatch.DrawString(font, scoreTxt2 + score2, new Vector2(graphics.GraphicsDevice.Viewport.Width - 300, 50), player2Color);
                bar.Draw(spriteBatch);
                bar2.Draw(spriteBatch);                      
                ball.Draw(spriteBatch);
                if (paused)
                {
                    spriteBatch.DrawString(font, pauseTxt,
                        new Vector2((graphics.GraphicsDevice.Viewport.Width / 2 - font.MeasureString(pauseTxt).X / 2 + 2),
                            graphics.GraphicsDevice.Viewport.Height / 2 - font.MeasureString(pauseTxt).Y / 2 + 2), Color.Black * .5f);
                    spriteBatch.DrawString(font, pauseTxt,
                        new Vector2((graphics.GraphicsDevice.Viewport.Width / 2 - font.MeasureString(pauseTxt).X / 2),
                            graphics.GraphicsDevice.Viewport.Height / 2 - font.MeasureString(pauseTxt).Y / 2), Color.Orange);
                }
                if (consoleActivated)
                {
                    commandsCount = 0;
                    spriteBatch.Draw(consoleBackground, consoleRect, Color.Black * .9f);                    
                    for (int i = 0; i < consoleCommands.Count; i++)
                    {
                        spriteBatch.DrawString(consoleFont, consoleCommands.ElementAt(i), new Vector2(consoleRect.Location.X + 10,
                            consoleRect.Location.Y + fontHeight * commandsCount), Color.White);
                        commandsCount++;
                    }
                    spriteBatch.Draw(inputBackground, inputRect, Color.WhiteSmoke);
                    if (inputCommand != null && inputCommand.Length > 0)
                    {
                        if (inputCommand.Length > maxChar)
                        {
                            spriteBatch.DrawString(consoleFont, inputCommand.Substring(inputCommand.Length - maxChar),
                                new Vector2(consoleRect.Location.X + 10, consoleRect.Height - fontHeight - 10), Color.DarkRed);
                            cursorRect.X = consoleRect.Location.X + 10 + maxChar * fontWidth;
                        }
                        else
                        {
                            spriteBatch.DrawString(consoleFont, inputCommand,
                                new Vector2(consoleRect.Location.X + 10, consoleRect.Height - fontHeight - 10), Color.DarkRed);
                            cursorRect.X = consoleRect.Location.X + 10 + inputCommand.Length * fontWidth;
                        }                        
                    }
                    double elapsedTime = gameTime.ElapsedGameTime.TotalMilliseconds;
                    cursorBlinkRate -= elapsedTime;
                    if (cursorBlinkRate < 0)
                    {
                        cursorBlinkRate = BlinkRate;
                    }
                    else
                        spriteBatch.Draw(consoleBackground, cursorRect, cursorRect, Color.Black);
                }

            spriteBatch.End();

            base.Draw(gameTime);
        }        

        //calculate distance between point2 (the ball's center) and 
        public double dCalc(Vector2 point1, Vector2 point2)
        {
            return Math.Abs(point2.X - point2.Y + point1.Y - point1.X)/ Math.Sqrt(2);
        }

        public double dCalc2(Vector2 point1, Vector2 point2)
        {
            return Math.Abs(point2.X + point2.Y - point1.Y - point1.X) / Math.Sqrt(2);
        }
    }
}
